package com.jakewharton.rxbinding4;

import android.app.Instrumentation;
import android.os.SystemClock;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import androidx.test.platform.app.InstrumentationRegistry;

public final class TouchUtilsBackport {
  /**
   * Simulate touching a view and dragging it to a specified location.
   *
   * @param v The view that should be dragged
   * @param gravity Which part of the view to use for the initial down event. A combination of
   * (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
   * @param toX Final location of the view after dragging
   * @param toY Final location of the view after dragging
   * @return distance in pixels covered by the drag
   */
  public static int dragViewTo(View v, int gravity, int toX, int toY) {
    int[] xy = new int[2];

    getStartLocation(v, gravity, xy);

    int fromX = xy[0];
    int fromY = xy[1];

    int deltaX = fromX - toX;
    int deltaY = fromY - toY;

    int distance = (int) Math.sqrt(deltaX * deltaX + deltaY * deltaY);
    drag(fromX, toX, fromY, toY, distance);

    return distance;
  }

  /**
   * Simulate touching a specific location and dragging to a new location.
   *
   * @param fromX X coordinate of the initial touch, in screen coordinates
   * @param toX Xcoordinate of the drag destination, in screen coordinates
   * @param fromY X coordinate of the initial touch, in screen coordinates
   * @param toY Y coordinate of the drag destination, in screen coordinates
   * @param stepCount How many move steps to include in the drag
   */
  public static void drag(float fromX, float toX, float fromY, float toY, int stepCount) {
    Instrumentation inst = InstrumentationRegistry.getInstrumentation();

    long downTime = SystemClock.uptimeMillis();
    long eventTime = SystemClock.uptimeMillis();

    float y = fromY;
    float x = fromX;

    float yStep = (toY - fromY) / stepCount;
    float xStep = (toX - fromX) / stepCount;

    MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0);
    inst.sendPointerSync(event);
    for (int i = 0; i < stepCount; ++i) {
      y += yStep;
      x += xStep;
      eventTime = SystemClock.uptimeMillis();
      event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
      inst.sendPointerSync(event);
    }

    eventTime = SystemClock.uptimeMillis();
    event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
    inst.sendPointerSync(event);
    inst.waitForIdleSync();
  }

  /**
   * Get the location of a view. Use the gravity param to specify which part of the view to
   * return.
   *
   * @param v View to find
   * @param gravity A combination of (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL,
   * RIGHT)
   * @param xy Result
   */
  private static void getStartLocation(View v, int gravity, int[] xy) {
    v.getLocationOnScreen(xy);

    int viewWidth = v.getWidth();
    int viewHeight = v.getHeight();

    switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
      case Gravity.TOP:
        break;
      case Gravity.CENTER_VERTICAL:
        xy[1] += viewHeight / 2;
        break;
      case Gravity.BOTTOM:
        xy[1] += viewHeight - 1;
        break;
      default:
        // Same as top -- do nothing
    }

    switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
      case Gravity.LEFT:
        break;
      case Gravity.CENTER_HORIZONTAL:
        xy[0] += viewWidth / 2;
        break;
      case Gravity.RIGHT:
        xy[0] += viewWidth - 1;
        break;
      default:
        // Same as left -- do nothing
    }
  }

  private TouchUtilsBackport() {
    throw new AssertionError("No instances");
  }
}
